/*
* Copyright 2003-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.attributes.compiler;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.StringTokenizer;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
import com.thoughtworks.qdox.JavaDocBuilder;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaField;
import com.thoughtworks.qdox.model.JavaMethod;
import com.thoughtworks.qdox.model.DocletTag;
import com.thoughtworks.qdox.model.JavaParameter;
/**
* Ant task to compile attributes. Usage:
*
* <pre><code>
* <taskdef resource="org/apache/commons/attributes/anttasks.properties"/>
*
* <attribute-compiler destDir="temp/"> attributepackages="my.attributes;my.otherattributes"
* <fileset dir="src/" includes="*.java"/>
* </attribute-compiler>
* </code></pre>
*
* <ul>
* <li>destDir<dd>Destination directory for generated source files
* <li>attributepackages<dd>A set of package names that will be automatically searched for attributes.
* </ul>
*
* The task should be run before compiling the Java sources, and will produce some
* additional Java source files in the destination directory that should be compiled
* along with the input source files. (See the overview for a diagram.)
*/
public class AttributeCompiler extends Task {
private final ArrayList fileSets = new ArrayList ();
private Path src;
private File destDir;
private int numGenerated;
private int numIgnored;
private String attributePackages = "";
public AttributeCompiler () {
}
public void setAttributePackages (String attributePackages) {
this.attributePackages = attributePackages;
}
public void addFileset (FileSet set) {
fileSets.add (set);
}
public void setDestdir (File destDir) {
this.destDir = destDir;
}
public void setSourcepathref (String pathref) {
String sourcePaths = project.getReference (pathref).toString ();
StringTokenizer tok = new StringTokenizer (sourcePaths, File.pathSeparator);
while (tok.hasMoreTokens ()) {
FileSet fs = new FileSet ();
fs.setDir (new File (tok.nextToken ()));
fs.setIncludes ("**/*.java");
fs.setProject (project);
addFileset (fs);
}
}
protected void copyImports (File source, PrintWriter dest) throws Exception {
BufferedReader br = new BufferedReader (new FileReader (source));
try {
String line = null;
while ((line = br.readLine ()) != null) {
if (line.startsWith ("import ")) {
dest.println (line);
}
}
} finally {
br.close ();
}
}
protected void addExpressions (DocletTag[] tags, PrintWriter pw, String collectionName, File sourceFile) {
addExpressions (tags, null, pw, collectionName, sourceFile);
}
protected void addExpressions (DocletTag[] tags, String selector, PrintWriter pw, String collectionName, File sourceFile) {
String fileName = sourceFile != null ? sourceFile.getPath ().replace ('\\', '/') : "<unknown>";
for (int i = 0; i < tags.length; i++) {
DocletTag tag = tags[i];
if (isAttribute (tag)) {
String tagName = tag.getName ();
String tagValue = tag.getValue ();
String expression = tagName + " " + tagValue;
expression = expression.trim ();
// Remove the second @-sign.
expression = expression.substring (1);
if (selector != null) {
if (expression.startsWith (".")) {
// We have selector, tag does...
String tagSelector = expression.substring (1, expression.indexOf (" "));
expression = expression.substring (expression.indexOf (" ")).trim ();
if (!selector.equals (tagSelector)) {
// ...but they didn't match.
continue;
}
} else {
// We have selector, but tag doesn't
continue;
}
} else {
// No selector, but tag has selector.
if (expression.startsWith (".")) {
continue;
}
}
pw.println (" {");
outputAttributeExpression (pw, expression, fileName, tag.getLineNumber (), "_attr");
pw.println (" Object _oattr = _attr; // Need to erase type information");
pw.println (" if (_oattr instanceof org.apache.commons.attributes.Sealable) {");
pw.println (" ((org.apache.commons.attributes.Sealable) _oattr).seal ();");
pw.println (" }");
pw.println (" " + collectionName + ".add ( _attr );");
pw.println (" }");
}
}
}
protected void outputAttributeExpression (PrintWriter pw, String expression, String filename, int line, String tempVariableName) {
AttributeExpressionParser.ParseResult result = AttributeExpressionParser.parse (expression, filename, line);
pw.print (" " + result.className + " " + tempVariableName + " = new " + result.className + "(");
boolean first = true;
Iterator iter = result.arguments.iterator ();
while (iter.hasNext ()) {
AttributeExpressionParser.Argument arg = (AttributeExpressionParser.Argument) iter.next ();
if (arg.field == null) {
if (!first) {
pw.print (", ");
}
first = false;
pw.print (arg.text);
}
}
pw.println (" // " + filename + ":" + line);
pw.println (");");
iter = result.arguments.iterator ();
while (iter.hasNext ()) {
AttributeExpressionParser.Argument arg = (AttributeExpressionParser.Argument) iter.next ();
if (arg.field != null) {
String methodName = "set" + arg.field.substring (0, 1).toUpperCase () + arg.field.substring (1);
pw.println (" " + tempVariableName + "." + methodName + "(\n" +
arg.text + " // " + filename + ":" + line + "\n" +
");");
}
}
}
protected boolean elementHasAttributes (JavaField[] fields) {
for (int i = 0; i < fields.length; i++) {
if (tagHasAttributes (fields[i].getTags ())) {
return true;
}
}
return false;
}
protected boolean elementHasAttributes (JavaMethod[] methods) {
for (int i = 0; i < methods.length; i++) {
if (tagHasAttributes (methods[i].getTags ())) {
return true;
}
}
return false;
}
/**
* Encodes a class name to the internal Java name.
* For example, an inner class Outer.Inner will be
* encoded as Outer$Inner.
*/
private void getTransformedQualifiedName (JavaClass type, StringBuffer sb) {
sb.append (type.getFullyQualifiedName ());
}
protected String getParameterTypes (JavaParameter[] parameters) {
StringBuffer sb = new StringBuffer ();
for (int i = 0; i < parameters.length; i++) {
if (i > 0) {
sb.append (",");
}
getTransformedQualifiedName (parameters[i].getType ().getJavaClass (), sb);
for (int j = 0; j < parameters[i].getType ().getDimensions (); j++) {
sb.append ("[]");
}
}
return sb.toString ();
}
protected void generateClass (JavaClass javaClass) throws Exception {
String name = null;
File sourceFile = null;
File destFile = null;
String packageName = null;
String className = null;
packageName = javaClass.getPackage ();
if (packageName == null) {
packageName = "";
}
if (javaClass.isInner ()) {
sourceFile = getSourceFile (javaClass);
String nonPackagePrefix = javaClass.getParent ().getClassNamePrefix ();
if (packageName.length () > 0) {
nonPackagePrefix = nonPackagePrefix.substring (packageName.length () + 1);
}
className = nonPackagePrefix + javaClass.getName ();
name = javaClass.getParent ().getClassNamePrefix () + javaClass.getName ();
} else {
name = javaClass.getFullyQualifiedName ();
sourceFile = getSourceFile (javaClass);
className = javaClass.getName ();
}
if (sourceFile == null) {
log ("Unable to find source file for: " + name);
}
destFile = new File (destDir, name.replace ('.', '/') + "$__attributeRepository.java");
/*if (javaClass.isAnonymous ()) {
log (javaClass.getName () + " is anonymous - ignoring.", Project.MSG_VERBOSE);
numIgnored++;
return;
}*/
if (!hasAttributes (javaClass)) {
if (destFile.exists ()) {
destFile.delete ();
}
return;
}
if (destFile.exists () && sourceFile != null && destFile.lastModified () >= sourceFile.lastModified ()) {
return;
}
numGenerated++;
destFile.getParentFile ().mkdirs ();
PrintWriter pw = new PrintWriter (new FileWriter (destFile));
try {
if (packageName != null && !packageName.equals ("")) {
pw.println ("package " + packageName + ";");
}
if (sourceFile != null) {
copyImports (sourceFile, pw);
}
StringTokenizer tok = new StringTokenizer (attributePackages, ";");
while (tok.hasMoreTokens ()) {
pw.println ("import " + tok.nextToken () + ".*;");
}
pw.println ("public class " + className + "$__attributeRepository implements org.apache.commons.attributes.AttributeRepositoryClass {");
{
pw.println (" private final java.util.Set classAttributes = new java.util.HashSet ();");
pw.println (" private final java.util.Map fieldAttributes = new java.util.HashMap ();");
pw.println (" private final java.util.Map methodAttributes = new java.util.HashMap ();");
pw.println (" private final java.util.Map constructorAttributes = new java.util.HashMap ();");
pw.println ();
pw.println (" public " + className + "$__attributeRepository " + "() {");
pw.println (" initClassAttributes ();");
pw.println (" initMethodAttributes ();");
pw.println (" initFieldAttributes ();");
pw.println (" initConstructorAttributes ();");
pw.println (" }");
pw.println ();
pw.println (" public java.util.Set getClassAttributes () { return classAttributes; }");
pw.println (" public java.util.Map getFieldAttributes () { return fieldAttributes; }");
pw.println (" public java.util.Map getConstructorAttributes () { return constructorAttributes; }");
pw.println (" public java.util.Map getMethodAttributes () { return methodAttributes; }");
pw.println ();
pw.println (" private void initClassAttributes () {");
addExpressions (javaClass.getTags (), pw, "classAttributes", sourceFile);
pw.println (" }");
pw.println ();
// ---- Field Attributes
pw.println (" private void initFieldAttributes () {");
pw.println (" java.util.Set attrs = null;");
JavaField[] fields = javaClass.getFields ();
for (int i = 0; i < fields.length; i++) {
JavaField member = (JavaField) fields[i];
if (member.getTags ().length > 0) {
String key = member.getName ();
pw.println (" attrs = new java.util.HashSet ();");
addExpressions (member.getTags (), pw, "attrs", sourceFile);
pw.println (" fieldAttributes.put (\"" + key + "\", attrs);");
pw.println (" attrs = null;");
pw.println ();
}
}
pw.println (" }");
// ---- Method Attributes
pw.println (" private void initMethodAttributes () {");
pw.println (" java.util.Set attrs = null;");
pw.println (" java.util.List bundle = null;");
JavaMethod[] methods = javaClass.getMethods ();
for (int i = 0; i < methods.length; i++) {
JavaMethod member = (JavaMethod) methods[i];
if (!member.isConstructor () && member.getTags ().length > 0) {
StringBuffer sb = new StringBuffer ();
sb.append (member.getName ()).append ("(");
sb.append (getParameterTypes (member.getParameters ()));
sb.append (")");
String key = sb.toString ();
pw.println (" bundle = new java.util.ArrayList ();");
pw.println (" attrs = new java.util.HashSet ();");
addExpressions (member.getTags (), null, pw, "attrs", sourceFile);
pw.println (" bundle.add (attrs);");
pw.println (" attrs = null;");
pw.println (" attrs = new java.util.HashSet ();");
addExpressions (member.getTags (), "return", pw, "attrs", sourceFile);
pw.println (" bundle.add (attrs);");
pw.println (" attrs = null;");
JavaParameter[] parameters = member.getParameters ();
for (int j = 0; j < parameters.length; j++) {
JavaParameter parameter = (JavaParameter) parameters[j];
pw.println (" attrs = new java.util.HashSet ();");
addExpressions (member.getTags (), parameter.getName (), pw, "attrs", sourceFile);
pw.println (" bundle.add (attrs);");
pw.println (" attrs = null;");
}
pw.println (" methodAttributes.put (\"" + key + "\", bundle);");
pw.println (" bundle = null;");
pw.println ();
}
}
pw.println (" }");
// ---- Constructor Attributes
pw.println (" private void initConstructorAttributes () {");
pw.println (" java.util.Set attrs = null;");
pw.println (" java.util.List bundle = null;");
JavaMethod[] constructors = javaClass.getMethods ();
for (int i = 0; i < constructors.length; i++) {
JavaMethod member = (JavaMethod) constructors[i];
if (member.isConstructor () && member.getTags ().length > 0) {
StringBuffer sb = new StringBuffer ();
sb.append ("(");
sb.append (getParameterTypes (member.getParameters ()));
sb.append (")");
String key = sb.toString ();
pw.println (" bundle = new java.util.ArrayList ();");
pw.println (" attrs = new java.util.HashSet ();");
addExpressions (member.getTags (), null, pw, "attrs", sourceFile);
pw.println (" bundle.add (attrs);");
pw.println (" attrs = null;");
JavaParameter[] parameters = member.getParameters ();
for (int j = 0; j < parameters.length; j++) {
JavaParameter parameter = (JavaParameter) parameters[j];
pw.println (" attrs = new java.util.HashSet ();");
addExpressions (member.getTags (), parameter.getName (), pw, "attrs", sourceFile);
pw.println (" bundle.add (attrs);");
pw.println (" attrs = null;");
}
pw.println (" constructorAttributes.put (\"" + key + "\", bundle);");
pw.println (" bundle = null;");
pw.println ();
}
}
pw.println (" }");
}
pw.println ("}");
pw.close ();
} catch (Exception e) {
pw.close ();
destFile.delete ();
throw e;
}
}
/**
* Finds the source file of a class.
*
* @param qualifiedName the fully qualified class name
* @return the file the class is defined in.
* @throws BuildException if the file could not be found.
*/
protected File getSourceFile (JavaClass javaClass) throws BuildException {
return javaClass.getSource ().getFile ();
}
protected boolean hasAttributes (JavaClass javaClass) {
if (tagHasAttributes (javaClass.getTags ()) ||
elementHasAttributes (javaClass.getFields ()) ||
elementHasAttributes (javaClass.getMethods ()) ) {
return true;
}
return false;
}
/**
* Tests if a tag is an attribute. Currently the test is
* only "check if it is defined with two @-signs".
*/
protected boolean isAttribute (DocletTag tag) {
return tag.getName ().length () > 0 && tag.getName ().charAt (0) == '@';
}
public void execute () throws BuildException {
destDir.mkdirs ();
numGenerated = 0;
try {
JavaDocBuilder builder = new JavaDocBuilder ();
for (int i = 0; i < fileSets.size (); i++) {
FileSet fs = (FileSet) fileSets.get (i);
DirectoryScanner ds = fs.getDirectoryScanner(project);
File fromDir = fs.getDir(project);
String[] srcFiles = ds.getIncludedFiles();
for (int j = 0; j < srcFiles.length; j++) {
String srcName = srcFiles[j];
File sourceFile = new File (fromDir, srcName);
builder.addSource (sourceFile);
}
}
JavaClass[] classes = builder.getClasses ();
for (int i = 0; i < classes.length; i++) {
generateClassAndInners (classes[i]);
}
log ("Generated attribute information for " + numGenerated + " classes. Ignored " + numIgnored + " classes.");
} catch (BuildException be) {
throw be;
} catch (Exception e) {
e.printStackTrace ();
throw new BuildException (e.toString (), e);
}
}
public void generateClassAndInners (JavaClass clazz) throws Exception {
generateClass (clazz);
JavaClass[] classes = clazz.getInnerClasses ();
for (int i = 0; i < classes.length; i++) {
generateClassAndInners (classes[i]);
}
}
/**
* Checks if a collection of XTags contain any tags specifying attributes.
*/
protected boolean tagHasAttributes (DocletTag[] tags) {
for (int i = 0; i < tags.length; i++) {
if (isAttribute (tags[i])) {
return true;
}
}
return false;
}
}